package dviz.visualSystem.impl;

import java.awt.Component;
import java.awt.Point;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import java.awt.event.MouseWheelEvent;
import java.awt.event.MouseWheelListener;
import java.net.URL;
import java.util.BitSet;
import java.util.LinkedList;
import java.util.List;

import com.gibbon.jme.context.JmeContext;
import com.jme.image.Image;
import com.jme.input.action.InputAction;
import com.jme.input.action.InputActionEvent;

/**
 * @author  zxq071000
 */
public class MouseInputAdapter implements MouseListener, MouseMotionListener,
		MouseWheelListener {
	InputAction target;
	LinkedList<InputActionEvent> events;
	//Because the update method which is used to process the input infomration
	//relies on the rendering loop, thus if rendering loop does not go on, we will
	//not get the input event, thus we notify the rendering method invoked once every time we
	// get a input event
	Component triggerComponent;
	public MouseInputAdapter(InputAction target) {
		this.target = target;
		events = new LinkedList<InputActionEvent>();
	}

	public static int WHEEL_AMP = 40; // arbitrary... Java's mouse wheel seems
	// to report something a lot lower than
	// lwjgl's

	private int currentWheelDelta;
	/**
	 * @uml.property  name="wheelDelta"
	 */
	private int wheelDelta;
	/**
	 * @uml.property  name="wheelRotation"
	 */
	private int wheelRotation;
	/**
	 * @uml.property  name="enabled"
	 */
	private boolean enabled = true;
	/**
	 * @uml.property  name="dragOnly"
	 */
	private boolean dragOnly = false;
	private BitSet buttons = new BitSet(3);

	private Point absPoint = new Point();
	private Point lastPoint = new Point(Integer.MIN_VALUE, Integer.MIN_VALUE);
	private Point currentDeltaPoint = new Point(0, 0);
	private Point deltaPoint = new Point(0, 0);

	private Component deltaRelative;

	public int getButtonIndex(String buttonName) {
		if ("MOUSE0".equalsIgnoreCase(buttonName)) {
			return 0;
		} else if ("MOUSE1".equalsIgnoreCase(buttonName)) {
			return 1;
		} else if ("MOUSE2".equalsIgnoreCase(buttonName)) {
			return 2;
		}

		throw new IllegalArgumentException("invalid buttonName: " + buttonName);
	}

	public boolean isButtonDown(int buttonCode) {
		return buttons.get(buttonCode);
	}

	public String getButtonName(int buttonIndex) {
		switch (buttonIndex) {
		case 0:
			return "MOUSE0";
		case 1:
			return "MOUSE1";
		case 2:
			return "MOUSE2";
		}
		throw new IllegalArgumentException("invalid buttonIndex: "
				+ buttonIndex);
	}

	/**
	 * @return
	 * @uml.property  name="wheelDelta"
	 */
	public int getWheelDelta() {
		return wheelDelta;
	}

	public int getXDelta() {
		if (deltaRelative != null) {
			if (!enabled) {
				return 0;
			}
			int rVal = (deltaRelative.getWidth() / 2) - absPoint.x;
			return (int) (rVal * -0.01f);
		}

		return deltaPoint.x;
	}

	public int getYDelta() {
		if (deltaRelative != null) {
			if (!enabled) {
				return 0;
			}
			int rVal = (deltaRelative.getHeight() / 2) - absPoint.y;
			return (int) (rVal * 0.05f);
		}

		return deltaPoint.y;
	}

	public int getXAbsolute() {
		return absPoint.x;
	}

	public int getYAbsolute() {
		return absPoint.y;
	}

	/**
	 * Swing events are put in here in the swing thread and removed from it in
	 * the update method. To flatline memory usage the LinkedList could be
	 * replaced by two ArrayLists but then one would need to synchronize
	 * insertions.
	 */
	private List<MouseEvent> swingEvents = new LinkedList<MouseEvent>();
	/**
	 * x position of last event that was processed by {@link #update}
	 */
	private int lastEventX;
	/**
	 * y position of last event that was processed by {@link #update}
	 */
	private int lastEventY;

	private synchronized void onMove(int xDelta, int yDelta, int newX,
			int newY, LinkedList<InputActionEvent> as) {
		int width = JmeContext.get().getRenderer().getWidth();
		int height = JmeContext.get().getRenderer().getHeight();

		if (xDelta != 0) {
			InputActionEvent actionEvent = new InputActionEvent();
			actionEvent.setTriggerName("X");
			actionEvent.setTriggerPosition((float) newX / (float) width);
			as.push(actionEvent);
		}
		if (yDelta != 0) {
			InputActionEvent actionEvent = new InputActionEvent();
			actionEvent.setTriggerName("Y");
			actionEvent.setTriggerPosition((float) newY / (float) height);
			as.push(actionEvent);
		}
	}

	private synchronized void onButton(int button, boolean pressed, int x,
			int y, LinkedList<InputActionEvent> as) {
		InputActionEvent actionEvent = new InputActionEvent();
		actionEvent.setTriggerName("MOUSE" + button);
		actionEvent.setTriggerPressed(pressed);
		as.push(actionEvent);
	}

	private synchronized void onWheel(int wheelDelta, int x, int y,
			LinkedList<InputActionEvent> events) {

	}

	public synchronized void update() {

		int x = lastEventX;
		int y = lastEventY;
		while (!swingEvents.isEmpty()) {
			MouseEvent event = swingEvents.remove(0);

			switch (event.getID()) {
			case MouseEvent.MOUSE_DRAGGED:
			case MouseEvent.MOUSE_MOVED:
				onMove(event.getX() - x, y - event.getY(), event.getX(), event
						.getY(), events);
				x = event.getX();
				y = event.getY();
				break;
			case MouseEvent.MOUSE_PRESSED:
			case MouseEvent.MOUSE_RELEASED:
				onButton(getJMEButtonIndex(event),
						event.getID() == MouseEvent.MOUSE_PRESSED,
						event.getX(), event.getY(), events);
				break;
			case MouseEvent.MOUSE_WHEEL:
				onWheel(((MouseWheelEvent) event).getUnitsToScroll()
						* WHEEL_AMP, event.getX(), event.getY(), events);

				break;
			default:
			}
		}
		lastEventX = x;
		lastEventY = y;
		wheelDelta = currentWheelDelta;
		currentWheelDelta = 0;
		deltaPoint.setLocation(currentDeltaPoint);
		currentDeltaPoint.setLocation(0, 0);

		while (events.size() != 0) {
			target.performAction(events.poll());
		}
	}

	public void setCursorVisible(boolean v) {
		// Ignored.
	}

	public void setup(Component c) {
		c.addMouseListener(this);
		c.addMouseMotionListener(this);
		c.addMouseWheelListener(this);
		this.triggerComponent=c;
	}

	public boolean isCursorVisible() {
		// always true
		return true;
	}

	public void setHardwareCursor(URL file) {
		// Ignored.
	}

	public void setHardwareCursor(URL file, int xHotspot, int yHotspot) {
		// Ignored.
	}

	public void setHardwareCursor(URL file, Image[] images, int[] delays,
			int xHotspot, int yHotspot) {
		// Ignored.
	}

	public void setCursorPosition(int x, int y) {
		absPoint.setLocation(x, y);
	}

	/**
	 * @return
	 * @uml.property  name="wheelRotation"
	 */
	public int getWheelRotation() {
		return wheelRotation;
	}

	public int getButtonCount() {
		return 3;
	}

	public void setRelativeDelta(Component c) {
		deltaRelative = c;
	}

	/**
	 * @return  Returns the enabled.
	 * @uml.property  name="enabled"
	 */
	public boolean isEnabled() {
		return enabled;
	}

	/**
	 * @param enabled  The enabled to set.
	 * @uml.property  name="enabled"
	 */
	public void setEnabled(boolean enabled) {
		this.enabled = enabled;
	}

	/**
	 * @return  Returns the dragOnly.
	 * @uml.property  name="dragOnly"
	 */
	public boolean isDragOnly() {
		return dragOnly;
	}

	/**
	 * @param dragOnly  The dragOnly to set.
	 * @uml.property  name="dragOnly"
	 */
	public void setDragOnly(boolean dragOnly) {
		this.dragOnly = dragOnly;
	}

	// **********************************
	// java.awt.event.MouseListener methods
	// **********************************

	public void mouseClicked(MouseEvent arg0) {
		; // ignore
		triggerComponent.repaint();
	}

	public void mousePressed(MouseEvent arg0) {
		if (!enabled) {
			return;
		}
		lastPoint.setLocation(arg0.getPoint());

		buttons.set(getJMEButtonIndex(arg0), true);

		swingEvents.add(arg0);
		triggerComponent.repaint();
	}

	private int getJMEButtonIndex(MouseEvent arg0) {
		int index;
		switch (arg0.getButton()) {
		default:
		case MouseEvent.BUTTON1: // left
			index = 0;
			break;
		case MouseEvent.BUTTON2: // middle
			index = 2;
			break;
		case MouseEvent.BUTTON3: // right
			index = 1;
			break;
		}
		return index;
	}

	public void mouseReleased(MouseEvent arg0) {
		if (!enabled) {
			return;
		}
		currentDeltaPoint.setLocation(0, 0);
		if (deltaRelative != null) {
			absPoint.setLocation(deltaRelative.getWidth() >> 1, deltaRelative
					.getHeight() >> 1);
		}

		buttons.set(getJMEButtonIndex(arg0), false);

		swingEvents.add(arg0);
		triggerComponent.repaint();
	}

	public void mouseEntered(MouseEvent arg0) {
		; // ignore for now
	}

	public void mouseExited(MouseEvent arg0) {
		; // ignore for now
	}

	// **********************************
	// java.awt.event.MouseWheelListener methods
	// **********************************

	public void mouseWheelMoved(MouseWheelEvent arg0) {
		if (!enabled) {
			return;
		}

		final int delta = arg0.getUnitsToScroll() * WHEEL_AMP;
		currentWheelDelta -= delta;
		wheelRotation -= delta;

		swingEvents.add(arg0);
		triggerComponent.repaint();
	}

	// **********************************
	// java.awt.event.MouseMotionListener methods
	// **********************************

	public void mouseDragged(MouseEvent arg0) {
		if (!enabled) {
			return;
		}

		absPoint.setLocation(arg0.getPoint());
		if (lastPoint.x == Integer.MIN_VALUE) {
			lastPoint.setLocation(absPoint.x, absPoint.y);
		}
		currentDeltaPoint.x = absPoint.x - lastPoint.x;
		currentDeltaPoint.y = -(absPoint.y - lastPoint.y);
		lastPoint.setLocation(arg0.getPoint());

		swingEvents.add(arg0);
		triggerComponent.repaint();
	}

	public void mouseMoved(MouseEvent arg0) {
		if (enabled && !dragOnly) {
			mouseDragged(arg0);
		}
		triggerComponent.repaint();
	}

	// /**
	// * Set up a canvas to fire mouse events via the input system.
	// * @param glCanvas canvas that should be listened to
	// * @param dragOnly true to enable mouse input to jME only when the mouse
	// is dragged
	// */
	// public static void setup( Canvas glCanvas, boolean dragOnly ) {
	// if (!isInited()) {
	// setProvider( InputSystem.INPUT_SYSTEM_AWT );
	// }
	// AWTMouseInput awtMouseInput = ( (AWTMouseInput) get() );
	// awtMouseInput.setEnabled( !dragOnly );
	// awtMouseInput.setDragOnly( dragOnly );
	// awtMouseInput.setRelativeDelta( glCanvas );
	// glCanvas.addMouseListener(awtMouseInput);
	// glCanvas.addMouseWheelListener(awtMouseInput);
	// glCanvas.addMouseMotionListener(awtMouseInput);
	// }
	//    
	public void clear() {
		this.buttons.clear();
	}

	public void clearButton(int buttonCode) {
		buttons.set(buttonCode, false);
	}

}
